home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / System / Goodies / CMIDI.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-12-04  |  29.0 KB  |  919 lines  |  [TEXT/KAHL]

  1. /*    CMIDI: Anodyne's interface to the MIDI Manager. Make sure this module is
  2.     in the same segment of the project as main(), so that the interrupt handler
  3.     here is loaded and locked (that's Playing Safe - if my reading of the
  4.     Segment Loader docs is correct, we're OK anyway if the handlers are
  5.     declared static) (Yup, 'cos Symantec tech. support told me so).
  6.         This module is essentially a simple channelising echo, plus facilities
  7.     to output arbitrary messages. We have a single input port, since those of
  8.     us lucky enough to own two keyboards can have the MIDI Manager do the
  9.     merging for us. Anodyne doesn't distinguish between input sources. We drive
  10.     several outputs; Anodyne keeps track of output (and channel) for each device.
  11.     We don't echo SysEx messages by default, but can choose to capture them
  12.     for the application.
  13.     
  14.     Nick Rothwell, September 1989. (rewrite in TC4.0, November 1990.) */
  15.  
  16. #include <TCL>
  17. #include "GLOBAL.h"
  18. #include "CMIDI.h"
  19. #include "CUtility.h"
  20. #include "CDashboard.h"
  21. #include "CDiagnostic.h"
  22. #include "midi.h"
  23. #include "chars.h"                /*    Need the OSLASH char for my signature! */
  24. #include "ICN.h"
  25. #include "STR.h"
  26.  
  27. #define ENABLED
  28.  
  29. #define BUFSIZE                  2048    /*    MIDI port buffer size. */
  30. #define MAX_MESSAGE               249    /*    Why isn't this defined in MIDI.h?? */
  31. #define PACKET_JUNK                 6    /*    Number of bytes other than data. */
  32. #define UNLOAD_TIMEOUT          1000    /*    Ticks. */
  33. #define POLL_TIME               100    /*    Msec. polling time for output routine
  34.                                         when the output buffer is empty. */
  35. #define BYTES_PER_MSEC             3
  36.                                     /*    Used to calculate the pause between
  37.                                         packets. Slightly conservative figure
  38.                                         (it's actually 3.125), for safety. */
  39. #define MAX_DIAGS               100    /*    Circular buffer of diag. messages. */
  40. #define MAX_QUEUE                10    /*    Output packet buffer. */
  41.  
  42. #define APPLE_MIDI_DRIVER    'amdr'
  43. #define APPLE_DRIVER_IN1    'Ain '
  44. #define APPLE_DRIVER_IN2    'Bin '
  45. #define APPLE_DRIVER_OUT1    'Aout'
  46. #define APPLE_DRIVER_OUT2    'Bout'
  47. #define ANODYNE_TIME        'Zeit'
  48. #define ANODYNE_IN            'In  '
  49. #define ANODYNE_OUT1        'Out1'
  50. #define ANODYNE_OUT2        'Out2'
  51.  
  52. /*    Locking/unlocking of input/echo (see below). I hope "++" and "--" are
  53.     atomic in this C compiler... */
  54.  
  55. #define LOCK    (G.lockDepth++)
  56. #define UNLOCK    (G.lockDepth--)
  57.  
  58. /*    A few words about System Exclusive capture and locking. The structure of
  59.     the various critical sections is, err, critical. If we want to capture a
  60.     sequence of system exclusive messages, without missing any or getting into
  61.     a race condition, we have to be very careful. To do so, we temporarily lock
  62.     out the reader, install a pointer to the capture buffer along with some
  63.     size counting stuff, and unlock. Whenever we've captured a complete SysEx
  64.     dump, we lock ourselves, uninstall the capture buffer, and unlock. This
  65.     puts us back into our default state: echo everything on a certain cable
  66.     and channel, ignore SysEx's.
  67.         There's a small fault here: we may be half-way through dealing with
  68.     a multi-packet system exclusive anyway, in which case the capture will
  69.     catch the end of it. I'm quite happy to ignore this (or fix it elsewhere)
  70.     rather than add complexity to the interrupt routines. */
  71.  
  72. /*    Transmission of messages. Sending a big SysEx dump as fast as we can is
  73.     a bad thing to do. We'll either overrun the SCC or the input buffer of the
  74.     receiving MIDI application. Even if the instrument inserts calls to Pause(),
  75.     each individual message might be extremely long (Ensoniq VFX anyone?). One
  76.     way out is to spin-wait between packets, but this is ugly and makes the
  77.     application's response sluggish. A better way is to tie the output port to
  78.     a timebase and timestamp the outgoing messages. Calls to Pause() simply
  79.     bump up the outgoing timestamp. This is better, but we still run the risk
  80.     of overflowing the destination's input buffer (and the SCC?). If we send
  81.     a D-50 dump like this, it will emerge with the right delays between
  82.     packets, but only if the 36K or so of data can sit somewhere in the meantime.
  83.     The only solution seems to be to do local output buffering. MM packets are
  84.     placed into a circular output buffer with timestamps (and dest. cables)
  85.     attached. A WakeUp routine which we attach to our timebase transmits any
  86.     messages (freeing the buffer slot) if the message timestamp is current (in
  87.     fact, slightly old). We have to use a
  88.     MIDIWakeUp routine to fire off messages; if it's a normal routine tied into
  89.     the event loop, we may be away from the event loop sufficiently long for
  90.     a lot of pending messages to pile up; they'll then all go off at once.
  91.     The wake-up routine runs at a default polling speed when the output buffers
  92.     are empty, and as soon as it sees any activity it outputs current messages,
  93.     re-scheduling itself for each pending message whose time has not come.
  94.     Once the buffer is empty again, it goes back to ticking at the polling
  95.     rate. This is a bit of a hack - the polling rate has to be high enough to
  96.     avoid a lot of messages piling up, but slow enough to leave the machine
  97.     alive (Mac Plus, anyone?) - but simpler than alternative schemes involving
  98.     rescheduling of a possibly active wake-up routine (I couldn't get that to
  99.     work reliably, so...). */
  100.  
  101. /*    Stop press - the non-polled version (better since it only runs the
  102.     WakeUp task when something goes into the buffer) works fine except under
  103.     the THINK C debugger! Film at 11. Non-polled is better, since we start a
  104.     WakeUp routine when something goes into the buffer (we assume this cancels
  105.     any outstanding WakeUp), and let it expire when the buffer becomes empty.
  106.     The debugger seems to interfere with this, so if you want the debugger,
  107.     turn on the flag for the polled version instead. It's slightly more
  108.     sluggish but functionally equivalent (I hope).
  109.         My guess: the Debugger grabs time when we restart the WakeUp routine
  110.     inside Dispatch - the result being that we cue it into the past. This
  111.     might explain the failure on the Mac Plus as well. */
  112.  
  113. /*    Input locking. We lock when silencing an output cable (there are tidier
  114.     ways, but...) The output interrupt routine also locks/unlocks when it
  115.     sends out continuation sequences (assumed to be for one cable at once).
  116.     Hence, the lock is a semaphore (almost) rather than a flag, just in case
  117.     some lock/unlock sequences coincide. */
  118.  
  119. /*    A static record to hold the variables needed by the input interrupt
  120.     routines. It doesn't need to be a record, but that makes things clearer.
  121.     Even with this, we still need the A5 magic to be able to call any
  122.     routines. */
  123.  
  124. typedef struct {
  125.     CABLE        cable;
  126.     MIDIPacket    packet;
  127. } OutputSlotRec, *OutputSlotPtr;
  128.  
  129. static struct
  130. {
  131.     Boolean pollingVersion;            /*    TEST: polled vs. non-polled version. */
  132.  
  133.     int lockDepth;                    /*    Lock out the input routine. */
  134.  
  135.   /*Capture information. */
  136.  
  137.     struct {
  138.         Boolean active;                /*    Are we capturing? */
  139.         Boolean error;                /*    Capture error detected? */
  140.         Boolean finished;            /*    We've got a fish. */
  141.         CInstrument *instrument;    /*    Owner of the capture data. */
  142.         int howMany;                /*    How many SysEx's to capture? */
  143.         long spaceTotal;            /*    Total capture space. */
  144.         Byte *base;                    /*    Base of capture area. */
  145.         Byte *ptr;                    /*    Destination for SysEx data. */
  146.         long spaceLeft;                /*    Space left. */
  147.     } capture;
  148.  
  149.     struct {
  150.         int refNum;                    /*    Input/timebase port refnums. */
  151.     } input, timebase;
  152.  
  153.     struct {
  154.         int refNum[NUM_CABLES];        /*    Reference num for output ports. */
  155.         RouteRec echoRoute;            /*    Route for echoing. */
  156.         RouteRec xmitRoute;            /*    Route for SysEx (etc.) transmission. */
  157.     } output;
  158.  
  159.     long dispatchTime;                /*    timestamp for outgoing messages. */
  160.  
  161.     struct {                        /*    The output packet queue. */
  162.         OutputSlotPtr queue;
  163.         int produce;
  164.         int consume;
  165.     } outputQueue;
  166.  
  167.   /*We can have one outstanding error at a time: */
  168.     struct {
  169.         int index;                    /*    If non-zero, it's the STR# index for an
  170.                                         error string. */
  171.         int extra;                    /*    Extra error info. */
  172.     } error;
  173.  
  174.     struct {
  175.         struct {
  176.             char *mesg;
  177.             long extra1, extra2;
  178.         } buffer[MAX_DIAGS];        /*    Circular buffer of diagnostics. */
  179.         int produce;
  180.         int consume;
  181.     } diags;
  182. } G;
  183.  
  184. /*    All the local, vanilla C routines may be called at interrupt time. No calls
  185.     to other segments are wise here, nor calls to ToolBox routines. */
  186.  
  187. /*    We maintain a circular buffer of diagnostics which the interrupt routine
  188.     is allowed to post to. */
  189.  
  190. LOCAL void postDiagnostic(char *mesg, long extra1, long extra2)
  191. {
  192.     G.diags.buffer[G.diags.produce].mesg = mesg;
  193.     G.diags.buffer[G.diags.produce].extra1 = extra1;
  194.     G.diags.buffer[G.diags.produce].extra2 = extra2;
  195.     G.diags.produce = (G.diags.produce+1) % MAX_DIAGS;
  196. }
  197.  
  198. LOCAL void postError(int mesg, int extra)
  199. {
  200.     G.error.index = mesg;
  201.     G.error.extra = extra;
  202. }
  203.  
  204. LOCAL void dealWithSysEx(MIDIPacketPtr packet)
  205. {
  206.     int len = packet->len - PACKET_JUNK;
  207.     Byte cont = packet->flags&midiContMask;
  208.  
  209.     if (!G.capture.active)
  210.         return;                                /*    Not capturing - ignore.*/
  211.  
  212.     if (len <= G.capture.spaceLeft) {
  213.         BlockMove(&packet->data[0], G.capture.ptr, len);
  214.         G.capture.ptr += len;
  215.         G.capture.spaceLeft -= len;
  216.  
  217. #if 0
  218.         postDiagnostic("Captured one", G.capture.howMany, G.capture.spaceLeft);
  219. #endif
  220.  
  221.       /*If this packet ends a complete SysEx, decrement the howMany count. */
  222.         if (cont == midiNoCont || cont == midiEndCont) {
  223.             if (--G.capture.howMany == 0) {    /*    End of dump. */
  224.                 G.capture.active = FALSE;    /*    Back to normal. We must leave
  225.                                                 the other capture fields for
  226.                                                 inspection. */
  227.                 G.capture.finished = TRUE;
  228.             }
  229.         }
  230.     } else {                                /*    Overflow! */
  231.         postError(OVERFLOW_index, len);
  232.         G.capture.active = FALSE;
  233.         G.capture.error = TRUE;
  234.     }
  235. }
  236.  
  237. LOCAL void dealWithInput(MIDIPacketPtr inPacket)
  238. {
  239.     Byte cont = inPacket->flags&midiContMask;
  240.     Byte status,                            /*    Status byte */
  241.          status0;                            /*    Status byte sans channel. */
  242.     int refNum;
  243.     CABLE cable;
  244.     MIDIPacket outPacket;
  245.     OSErr err;
  246.  
  247.     switch (cont) {
  248.         case midiNoCont:
  249.             status = inPacket->data[0];
  250.             status0 = status&0xF0;
  251.  
  252.             switch (status0) {
  253.                 case 0x80:                /*    NOTE OFF. */
  254.                 case 0x90:                /*    NOTE ON. */
  255.                 case 0xA0:                /*    POLYPHONIC AFTERTOUCH. */
  256.                 case 0xB0:                /*    CONTROL CHANGE. */
  257.                 case 0xC0:                /*    PROGRAM CHANGE. */
  258.                 case 0xD0:                /*    CHANNEL AFTERTOUCH. */
  259.                 case 0xE0:                /*    PITCH-WHEEL. */
  260.                     /*    We echo if we have a valid output cable. Note that we
  261.                         *don't* echo if we're capturing SysEx - this is because
  262.                         (for example) we might send patch changes in our unload
  263.                         routine, and having them cause non-SysEx junk to come
  264.                         back (e.g. VFX Presets) and get echoed will screw things
  265.                         quite royally. */
  266.                     cable = G.output.echoRoute.cable;
  267.                     if ((cable != NONE) && (!G.capture.active)) {
  268.                         outPacket = *inPacket;    /*    Copy the whole record (!) */
  269.                         outPacket.data[0] =
  270.                             status0 + (G.output.echoRoute.channel-1);
  271.                                                 /*    Channelise... */
  272.                         refNum = G.output.refNum[cable];
  273.                         err = MIDIWritePacket(refNum, &outPacket);
  274.                         if (err != noErr)
  275.                             postError(ECHOERR_index, err);
  276.                     }
  277.                     
  278.                     break;
  279.                     
  280.                 case 0xF0:                /*    SYSTEM MESSAGE... */
  281.                     switch (status) {
  282.                                      /*    SYSTEM COMMON: */
  283.                         case 0xF0:        /*    START OF EXCLUSIVE. */
  284.                             dealWithSysEx(inPacket);
  285.                             break;
  286.                 
  287.                         case 0xF7:        /*    END OF EXCLUSIVE. */
  288.                             postError(DATAF7_index, 0);
  289.                                 /*    F7 as the status of a non-continuation? */
  290.                             break;
  291.                 
  292.                         case 0xF1:        /*    MIDI TIME CODE 1/4 FRAME. */
  293.                         case 0xF2:        /*    SONG POSITION POINTER. */
  294.                         case 0xF3:        /*    SONG SELECT. */
  295.                         case 0xF4:        /*    undefined. */
  296.                         case 0xF5:        /*    undefined. */
  297.                         case 0xF6:        /*    TUNE REQUEST. */
  298.                 
  299.                                        /*    REALTIME: */
  300.                         case 0xF8:        /*    TIMING CLOCK. */
  301.                         case 0xF9:        /*    undefined. */
  302.                         case 0xFA:        /*    START. */
  303.                         case 0xFB:        /*    CONTINUE. */
  304.                         case 0xFC:        /*    STOP. */
  305.                         case 0xFD:        /*    undefined. */
  306.                         case 0xFE:        /*    ACTIVE SENSING. */
  307.                         case 0xFF:        /*    SYSTEM RESET. */
  308.                             break;    /*    Ignore all this junk. MIDI Manager
  309.                                         handles most of it anyway. */
  310.                         }
  311.                     break;
  312.                 
  313.                 default:            /*    Not a status byte? */
  314.                     postError(DATA0_index, (int) status);
  315.             }
  316.             break;
  317.  
  318.         case midiStartCont:            /*    These must all be SysEx fragments. */
  319.         case midiMidCont:
  320.         case midiEndCont:
  321.             dealWithSysEx(inPacket);
  322.     }
  323. }
  324.  
  325. /*    This is the read hook. */
  326.  
  327. LOCAL pascal int myReadHook(MIDIPacketPtr myPacket, long myRefCon)
  328.                             /*    The refCon is the saved A5 for this context. */
  329. {
  330.     long safeA5 = SetA5(myRefCon);    /*    Is "SetA5()" in Inside Mac anywhere? */
  331.     Byte flags;
  332.     
  333.     if (G.lockDepth > 0) {            /*    We're locked. Catch it next time. */
  334.         (void) SetA5(safeA5);
  335.         return midiKeepPacket;
  336.     } else {
  337.         flags = myPacket->flags;
  338.         switch (flags&midiTypeMask) {
  339.             case midiMsgType:        /*    MIDI data. */
  340.                 dealWithInput(myPacket);
  341.                 break;
  342.     
  343.             case midiMgrType:        /*    Message from MIDI manager. */
  344.                 postError(UNEXPECTEDMSG_index,
  345.                           *((int *) &myPacket->data[0])
  346.                          );    /*    data[0..1] is a word containing the error. */
  347.                 break;
  348.     
  349.             default:
  350.                 break;                /*    Ignore unknown message types. */
  351.         }
  352.         
  353.         (void) SetA5(safeA5);
  354.         return midiMorePacket;
  355.     }
  356. }
  357.  
  358. /*    This is the time hook procedure. When it's called, it outputs any messages
  359.     which are behind the current time. If that exhausts the messages, it just
  360.     schedules itself for the next poll. Otherwise, it looks at the next message,
  361.     and schedules a wake-up for itself at that time it.
  362.         I would have liked to perform the trick used by MIDIArp and launch
  363.     packets slightly ahead of time, but they might be continuation packets and
  364.     might interfere with anything echoed directly by the read-hook.
  365.         Invariant: the messages in the output queue are in time-ascending
  366.     order. */
  367.  
  368. /*    It's an important property that the readHook
  369.     doesn't echo SysEx messages; otherwise, we'd have to have some safe way of
  370.     waiting for it to come out of any run of continued packets. As it is,
  371.     with have to deal with the inverse case; when we want to transmit a long
  372.     message, we must lock out the readHook so that it doesn't insert one of its
  373.     own messages in the middle. */
  374.  
  375. LOCAL pascal void myTimeHook(long currTime, long refCon)
  376. {
  377.     long safeA5 = SetA5(refCon);    /*    Is "SetA5()" in Inside Mac anywhere? */
  378.     int i;
  379.     OutputSlotPtr slot;
  380.     MIDIPacketPtr packet;
  381.     long nextTStamp;
  382.     int refNum;
  383.     Byte cont;
  384.     OSErr err;
  385.  
  386. #if 0
  387.     postDiagnostic("time hook", 0L, 0L);
  388. #endif
  389.  
  390.     for (;;) {
  391.         if (G.outputQueue.consume == G.outputQueue.produce) {
  392.             if (G.pollingVersion)
  393.                 MIDIWakeUp(G.timebase.refNum, currTime + POLL_TIME,
  394.                            0, (ProcPtr) &myTimeHook
  395.                           );        /*    Schedule ourself for another poll. */
  396.  
  397.             break;
  398.         } else {
  399.             slot = &G.outputQueue.queue[G.outputQueue.consume];
  400.             packet = &slot->packet;
  401.             nextTStamp = packet->tStamp;
  402.             if (nextTStamp <= currTime) {
  403. #if 0
  404.                 postDiagnostic("time: transmitting", currTime, 0L);
  405. #endif
  406.                                     /*    Transmit the packet. */
  407.                 refNum = G.output.refNum[slot->cable];
  408.  
  409.               /*VORSICHT: we have to be careful of continuation flags. If we
  410.                 have a number of contination packets with widely-spaced
  411.                 timestamps, we don't want the echo hook to interfere. So, we
  412.                 must do some locking (before startCont and after endCont). */
  413.  
  414.                 cont = packet->flags&midiContMask;
  415.  
  416.                 if (cont == midiStartCont)  LOCK;
  417.  
  418.                 err = MIDIWritePacket(refNum, packet);
  419.                 if (err != noErr)  postError(WRITEPACKET_index, err);
  420. #if 0
  421.                 postDiagnostic("xmit: flags, data[0..3]",
  422.                                (long) packet->flags,
  423.                                (long) *((long *)(&packet->data[0]))
  424.                               );
  425. #endif
  426.  
  427.                 if (cont == midiEndCont)  UNLOCK;
  428.  
  429.                 G.outputQueue.consume = (G.outputQueue.consume+1) % MAX_QUEUE;
  430.                                     /*    Step onto the next message. */
  431.             } else {                /*    Next message is too far ahead... */
  432. #if 0
  433.                 postDiagnostic("time: too far ahead", currTime, nextTStamp);
  434. #endif
  435.                 MIDIWakeUp(G.timebase.refNum, nextTStamp,
  436.                            0, (ProcPtr) &myTimeHook
  437.                           );        /*    Schedule ourself for the next one... */
  438.                 break;                /*    ..and return. */
  439.             }
  440.         }
  441.     }
  442.  
  443.     (void) SetA5(safeA5);
  444. }
  445.  
  446. /*    Now we're out of the seat-of-the-pants interrupt stuff and into normal
  447.     Class methods. */
  448.  
  449. /*    IMIDI (and friends) mustn't generate diagnostics - see IAnodyneApp(). */
  450.  
  451. NEW void CMIDI::IMIDI(Boolean pollingVersion)
  452. {
  453.     Handle myIcon;
  454.     MIDIPortParams params;
  455.     OSErr err;
  456.     int i;
  457.     StringPtr name;
  458.  
  459. #ifdef ENABLED
  460.   /*Set up the G values. */
  461.     G.pollingVersion = pollingVersion;
  462.     G.lockDepth = 0;
  463.  
  464.     G.capture.active = FALSE;
  465.     G.capture.error = FALSE;
  466.     G.capture.finished = FALSE;
  467.     G.capture.base = NULL;
  468.  
  469.     G.output.echoRoute.cable = NONE;
  470.     G.output.echoRoute.channel = 0;
  471.     G.output.xmitRoute.cable = NONE;
  472.     G.output.xmitRoute.channel = 0;
  473.  
  474.     G.error.index = 0;
  475.     G.error.extra = 0;
  476.     G.diags.produce = 0;
  477.     G.diags.consume = 0;
  478.  
  479.     G.dispatchTime = 0L;
  480.  
  481.     G.outputQueue.queue =
  482.         (OutputSlotPtr)
  483.             gUtility->MustNewPtr(((long) MAX_QUEUE) * sizeof(OutputSlotRec));
  484.  
  485.     G.outputQueue.produce = 0;
  486.     G.outputQueue.consume = 0;
  487.  
  488.   /*Get an ICON (in fact, the first one in our bundle ICN#) */
  489.     myIcon = gUtility->MustGetResource('ICN#', BUNDLE_icns);
  490.  
  491.   /*Do we have the MIDI Manager installed? */
  492.     gUtility->Assert("Can't connect to the MIDI Manager",
  493.                      SndDispVersion(midiToolNum) != 0L
  494.                     );
  495.  
  496.   /*Try to sign in. */
  497.     name = gUtility->ApplicationName();
  498.     g_Diagnostic("Signing in \"%#s\"", name);
  499.     if (name[0] > 31)  g_Die("MIDISignIn name: \"%#s\"", name);
  500.  
  501.     gUtility->HardCheckOSError(MIDISignIn(SIGNATURE, 0L, myIcon, (StringPtr) "\pHello"));
  502.  
  503.  
  504.   /*First of all, create and add the (invisible) timebase port. We use this
  505.     for timestamping output messages. */
  506.  
  507.     params.portID = ANODYNE_TIME;
  508.     params.portType = midiPortTypeTimeInv;
  509.     params.timeBase = 0;        /*    A timebase with a timebase? You jest, Sir. */
  510.     params.readHook = NULL;
  511.     params.initClock.sync = midiInternalSync;
  512.     params.initClock.curTime = 0L;
  513.     params.initClock.format = midiFormatMSec;
  514.  
  515.     params.refCon = SetCurrentA5();
  516.  
  517.     GetIndString(params.name, MIDIMANAGER_strs, TIMEBASE_index);
  518.     
  519.     err = MIDIAddPort(SIGNATURE, BUFSIZE, &G.timebase.refNum, ¶ms);
  520.     if (err != midiVConnectMade)  gUtility->HardCheckOSError(err);
  521.  
  522.   /*Add the input port. Note that we give it a timebase - that's just so that
  523.     we get another chance at any packets we turn down from the readhook. */
  524.  
  525.     params.portID = ANODYNE_IN;
  526.     params.portType = midiPortTypeInput;
  527.     params.timeBase = G.timebase.refNum;
  528.     params.offsetTime = 0L;
  529.     params.readHook = (Ptr) &myReadHook;
  530.  
  531.     GetIndString(params.name, MIDIMANAGER_strs, INPUTCABLE_index);
  532.     
  533.     err = MIDIAddPort(SIGNATURE, BUFSIZE, &G.input.refNum, ¶ms);
  534.     if (err != midiVConnectMade)  gUtility->HardCheckOSError(err);
  535.     
  536.   /*Connect the output ports. Not many of the fields have to change in the
  537.     params record. */
  538.  
  539.     params.portType = midiPortTypeOutput;
  540.     params.timeBase = G.timebase.refNum;
  541.     params.readHook = 0L;
  542.     params.refCon = 0L;
  543.  
  544.     for (i = 0; i < NUM_CABLES; i++) {
  545.         params.portID = ANODYNE_OUT1 + i;        /*    'Out1', 'Out2' etc. */
  546.         GetIndString(params.name, MIDIMANAGER_strs, OUTPUT1STCABLE_index + i);
  547.         err = MIDIAddPort(SIGNATURE, BUFSIZE,
  548.                           &G.output.refNum[i], ¶ms
  549.                          );
  550.         if (err != midiVConnectMade)  gUtility->HardCheckOSError(err);
  551.     }
  552.     
  553.     PatchMeIn();
  554.  
  555.   /*Start the clock. */
  556.     MIDIStartTime(G.timebase.refNum);
  557.  
  558.     if (G.pollingVersion)
  559.         MIDIWakeUp(G.timebase.refNum, 0L,
  560.                    (long) POLL_TIME, (ProcPtr) &myTimeHook
  561.                   );            /*    Kick off the output routine. */
  562. #endif
  563. }
  564.  
  565. OVERRIDE void CMIDI::Dispose()
  566. {
  567. #ifdef ENABLED
  568.     SetCursor(*gWatchCursor);
  569.     CoolOff();
  570.  
  571.     MIDISignOut(SIGNATURE);
  572. #endif
  573.     inherited::Dispose();
  574. }
  575.  
  576. /*    StatusCheck(): the application calls this method periodically to make sure
  577.     the MIDI module is happy. StatusCheck is also responsible for managing the
  578.     asynchronous UNLOAD's - it repaints the dashboard status bar, and has the
  579.     responsibility of creating a new patch file when a capture completes. */
  580.  
  581. NEW void CMIDI::StatusCheck()
  582. {
  583.     Str255 buff;
  584.  
  585. #ifdef ENABLED
  586.   /*Check for diagnostics first. */
  587.     while (G.diags.consume != G.diags.produce) {
  588.         g_Diagnostic("StatusCheck: %s [%08lx] [%08lx]",
  589.                      G.diags.buffer[G.diags.consume].mesg,
  590.                      G.diags.buffer[G.diags.consume].extra1,
  591.                      G.diags.buffer[G.diags.consume].extra2
  592.                     );
  593.         G.diags.consume = (G.diags.consume+1) % MAX_DIAGS;
  594.     }
  595.  
  596.   /*Now, check the status of the capture. If active, repaint status bar... */
  597.     if (G.capture.active) {
  598.         gDashboard->StatusBar(G.capture.spaceTotal - G.capture.spaceLeft,
  599.                               G.capture.spaceTotal
  600.                              );
  601.     } else if (G.capture.finished) {    /*    Capture finished? */
  602.         G.capture.finished = FALSE;        /*    Clear down flag. */
  603.         gDashboard->NoStatusBar();
  604.  
  605.         if (G.capture.spaceLeft != 0L)
  606.             postError(SIZEMISMATCH_index, 0);
  607.         else if (G.capture.error)
  608.             G.capture.error = FALSE;    /*    And just trash the data. */
  609.         else
  610.             G.capture.instrument->DigestDump(G.capture.base);
  611.  
  612.         DisposPtr(G.capture.base);
  613.         G.capture.base = NULL;
  614.     }
  615.  
  616.     if (G.error.index != 0) {
  617.         gUtility->Notify(MIDIMANAGER_strs, G.error.index, G.error.extra);
  618.         G.error.index = 0;
  619.     }
  620. #endif
  621. }
  622.  
  623. /*    Transmit() and Pause() are the methods used to send out messages. We put
  624.     all messages into an output buffer which is serviced by the timer hook.
  625.     All messages are sent at the current time (w.r.t. our timebase) or perhaps
  626.     into the future - all that Pause() does is advance the dispatch time to
  627.     delay the next packet's transmission time.
  628.         We do our own output buffering because we have no idea how much input
  629.     buffering the destination has. We could send an entire patch bank, with
  630.     correctly staggered timestamps, in one go, but they'd almost certainly
  631.     overrun a buffer somewhere. Our output buffer is (of course) limited in
  632.     size - if we fill it up, we have no alternative but to spin-wait for a
  633.     slot to become free. The idea is that single patches can be dispatched and
  634.     not waited for (good for the program's response to the user), but entire
  635.     patch banks will be waited for until they're (almost) concluded. */
  636.  
  637. NEW void CMIDI::Pause(int msec)
  638. {
  639. #ifdef ENABLED
  640.     long currTime = MIDIGetCurTime(G.timebase.refNum);
  641.  
  642.   /*Bring dispatch time up-to-date, if necessary. */
  643.     G.dispatchTime = Max(G.dispatchTime, currTime);
  644.  
  645.     G.dispatchTime += msec;
  646. #endif
  647. }
  648.  
  649. PRIVATE void CMIDI::Dispatch(RouteRec route, MIDIPacketPtr p, Byte cont)
  650. {
  651.     long currTime;
  652.     OutputSlotPtr slot;
  653.     int followingSlot;
  654.  
  655. #if 0
  656.     g_Diagnostic("Dispatch (cable=%d, continuation=%02x)",route.cable, cont);
  657. #endif
  658.  
  659.     followingSlot = (G.outputQueue.produce+1) % MAX_QUEUE;
  660.     while (followingSlot == G.outputQueue.consume)
  661.         /*Nothing*/;                /*    We spin-wait while the next packet would
  662.                                         make the queue pointers coincide (yup,
  663.                                         we can't distinguish empty vs. full!).*/
  664.  
  665.     slot = &G.outputQueue.queue[G.outputQueue.produce];
  666.     
  667.     gUtility->Assert("Dispatch(NOWHERE)", route.cable != NONE);
  668.     slot->cable = route.cable;
  669.     currTime = MIDIGetCurTime(G.timebase.refNum);
  670.  
  671.   /*Bring dispatch time up-to-date, if necessary. */
  672. #if 0
  673.     g_Diagnostic("Bring up to date (dispatch=%08lx, current=%08lx)",
  674.                  G.dispatchTime, currTime
  675.                 );
  676. #endif
  677.     G.dispatchTime = Max(G.dispatchTime, currTime);
  678.  
  679.     p->flags = cont | midiTimeStampValid;
  680.     p->tStamp = G.dispatchTime;
  681.  
  682.     slot->packet = *p;        /*    Copy packet record to output buffer. */
  683.  
  684.     G.outputQueue.produce = followingSlot;
  685.  
  686.     if (!G.pollingVersion) {
  687.         MIDIWakeUp(G.timebase.refNum, NULL, NULL, NULL);
  688.                             /*    We *must* cancel any wake-up call because we're
  689.                                 about to call it at non-interrupt, and don't
  690.                                 want two at once...! */
  691.  
  692.         myTimeHook(currTime, SetCurrentA5());
  693.     }
  694. }
  695.  
  696. PRIVATE void CMIDI::Transmit(RouteRec route, Byte *mesg,
  697.                              long len, Boolean showStatus
  698.                             )
  699. {
  700.     int thisTime;
  701.     Byte cont;
  702.     Byte *ptr;
  703.     MIDIPacket packet;
  704.     long remaining = len;
  705.  
  706. #ifdef ENABLED
  707. #if 0
  708.     g_Diagnostic("Transmit(len=%ld, mesg=[%02x, ...]", len, *mesg);
  709. #endif
  710.  
  711.     if (len <= MAX_MESSAGE) {        /*    Can do in a single packet. */
  712.         BlockMove(mesg, &packet.data[0], len);
  713.         packet.len = len + PACKET_JUNK;
  714.         Dispatch(route, &packet, midiNoCont);
  715.         Pause(len / BYTES_PER_MSEC);
  716.     } else {
  717.         cont = midiStartCont;
  718.         ptr = mesg;
  719.         remaining = len;
  720.         while (remaining > 0) {
  721.             thisTime = Min(remaining, MAX_MESSAGE);
  722.             BlockMove(ptr, &packet.data[0], thisTime);
  723.             packet.len = thisTime + PACKET_JUNK;
  724.             Dispatch(route, &packet, cont);
  725.             ptr += thisTime;
  726.             remaining -= thisTime;
  727.             cont = (remaining > MAX_MESSAGE) ? midiMidCont : midiEndCont;
  728.           /*Now, we place a short pause before the next packet (if any), just
  729.             to avoid SCC overruns. */
  730.             Pause(thisTime / BYTES_PER_MSEC);
  731.             if (showStatus)  gDashboard->StatusBar(ptr - mesg, len);
  732.         }
  733.     }
  734. #endif
  735. }
  736.  
  737. NEW void CMIDI::Silence()
  738. {
  739.     Byte mesg[3];
  740.     
  741.     if (G.output.echoRoute.cable != NONE) {
  742.         mesg[0] = CONTROL_CHANGE + (G.output.echoRoute.channel - 1);
  743.         mesg[1] = ALL_NOTES_OFF;
  744.         mesg[2] = 0x00;
  745.         Transmit(G.output.echoRoute, mesg, 3, FALSE);
  746.     }
  747. }
  748.  
  749. NEW void CMIDI::SetOutput(RouteRec route)
  750. {
  751.     G.output.xmitRoute = route;
  752. }
  753.  
  754. NEW void CMIDI::ClearOutput()
  755. {
  756.     G.output.xmitRoute.cable = NONE;
  757.     G.output.xmitRoute.channel = 0;
  758. }
  759.  
  760. /*    Select a default for midi idling and for messages; or, nothing (if cable
  761.     is NONE). The lock is just to stop any note-on messages sneaking in after
  762.     we've silenced the channel. */
  763.  
  764. NEW void CMIDI::StartEchoing(RouteRec route)
  765. {
  766.     LOCK;
  767.  
  768.     if (route.cable != NONE && (route.cable < 0 || route.cable >= NUM_CABLES))
  769.         g_Die("CMIDI::Select: bad cable (%d)", route.cable);
  770.  
  771.     if (route.cable != G.output.echoRoute.cable
  772.         || route.channel != G.output.echoRoute.channel
  773.        )
  774.         Silence();
  775.  
  776.     G.output.echoRoute = route;
  777.     
  778.     UNLOCK;
  779. }
  780.  
  781. NEW void CMIDI::StopEchoing()
  782. {
  783.     RouteRec route;
  784.  
  785.     route.cable = NONE;
  786.     route.channel = 0;
  787.     StartEchoing(route);
  788. }
  789.  
  790. NEW void CMIDI::PatchChange(int patch)
  791. {
  792.     Byte mesg[2];
  793.     
  794.     mesg[0] = PATCH_CHANGE + (G.output.xmitRoute.channel - 1);
  795.     mesg[1] = patch;
  796.     Transmit(G.output.xmitRoute, mesg, 2, FALSE);
  797. }
  798.  
  799. PRIVATE long CMIDI::CalcSysExLength(Byte *mesg)
  800. {
  801.     Byte *p = mesg;
  802.     
  803.     while (*p != EOX)  p++;
  804.     return((p - mesg) + 1);
  805. }
  806.  
  807. /*    SendSysEx() - we have the option of displaying a status bar *just for this
  808.                   SysEx*, in case we want to do that for a very long one. */
  809.  
  810. NEW void CMIDI::SendSysEx(Byte *mesg, Boolean showStatus)
  811. {
  812.     Transmit(G.output.xmitRoute, mesg, CalcSysExLength(mesg), showStatus);
  813.     if (showStatus)  gDashboard->NoStatusBar();
  814. }
  815.  
  816. NEW void CMIDI::InitiateCapture(CInstrument *instrument)
  817. {
  818. #ifdef ENABLED
  819.     Byte *buff = (Byte *) gUtility->MustNewPtr(instrument->env.captureSize);
  820.  
  821.     G.capture.instrument = instrument;
  822.     G.capture.howMany = instrument->env.numMessages;
  823.     G.capture.spaceTotal = instrument->env.captureSize;
  824.     G.capture.spaceLeft = G.capture.spaceTotal;
  825.     G.capture.base = buff;
  826.     G.capture.ptr = buff;
  827.     G.capture.active = TRUE;                /*    Let rip... */
  828.  
  829.     gDashboard->StatusBar(0, 1);            /*    Draw (empty) status bar... */
  830.     instrument->RequestDump();                /*    Just in case that's needed. */
  831. #endif
  832. }
  833.  
  834. /*    CancelCapture() - Now presumably, the application called CMIDI::Unloading()
  835.                       and got back a reply of TRUE, and called us here. Now,
  836.                       the capture might have just finished (finished==TRUE or
  837.                       error==TRUE), or it might still be going (active==TRUE).
  838.                       Whichever way, we should be able to close it down and
  839.                       dispose of the buffer. This *wouldn't* be the case if
  840.                       there was a call to CMIDI::StatusCheck() in the middle.
  841.                       StatusCheck() sets the buffer base to NULL to allow us a
  842.                       sanity check, since unfortunately, we can't safely do
  843.                       a check by look at the flags - they may get nuked
  844.                       at interrupt level while we're looking at them...! */
  845.  
  846. NEW void CMIDI::CancelCapture()
  847. {
  848.     gUtility->Assert("CancelCapture", G.capture.base != NULL);
  849.  
  850.     G.capture.active = FALSE;                /*    First, pull the plug. */
  851.     G.capture.finished = FALSE;
  852.     G.capture.error = FALSE;                /*    Don't confuse next call to
  853.                                                 StatusCheck(). */
  854.  
  855.     DisposPtr(G.capture.base);
  856.     G.capture.base = NULL;
  857.  
  858.     gDashboard->NoStatusBar();                /*    Turn off the thermometer. */
  859. }
  860.  
  861. /*    CoolOff() - if we do a LOAD, the output buffer might still have some stuff
  862.                 in it which is waiting for transmission. Hitting QUIT right
  863.                 away might lose the tail-end of a bank transmission. So, just
  864.                 before quitting, we call CoolOff() to spin-wait until the output
  865.                 buffer is empty. */
  866.  
  867. NEW void CMIDI::CoolOff()
  868. {
  869. #ifdef ENABLED
  870.     while (G.outputQueue.consume != G.outputQueue.produce)
  871.         SystemTask();
  872. #endif
  873. }
  874.  
  875. /*    Unloading() - we don't allow a Load() while we're unloading (contention
  876.                   for the thermometer; besides, it'll overrun our buffers).
  877.                   Also, if we're unloading, we enable CANCEL. Vorsicht: just
  878.                   because you call Unloading() and get TRUE, doesn't mean it
  879.                   will be true a moment later. Blocking LOAD is harmless, but
  880.                   be careful about CANCEL. Oh yes, allowing UNLOAD while
  881.                   unloading is pretty much a no-no, as well. */
  882.  
  883. NEW Boolean CMIDI::Unloading()
  884. {
  885.     return G.capture.active;
  886. }
  887.  
  888. /*    PatchMeIn(): shouldn't go into the final release, but convenient for
  889.     testing. Connects up to the Apple MIDI Drivers. */
  890.  
  891. PRIVATE void CMIDI::CheckConnect(OSErr err)
  892. {
  893.     if (err != midiVConnectErr)
  894.         gUtility->HardCheckOSError(err);
  895. }
  896.  
  897. PRIVATE void CMIDI::PatchMeIn()
  898. {
  899.   /*Both real input ports to my single input port: */
  900.     CheckConnect(MIDIConnectData(APPLE_MIDI_DRIVER, APPLE_DRIVER_IN1,
  901.                                  SIGNATURE, ANODYNE_IN
  902.                                 )
  903.                 );
  904.     CheckConnect(MIDIConnectData(APPLE_MIDI_DRIVER, APPLE_DRIVER_IN2,
  905.                                  SIGNATURE, ANODYNE_IN
  906.                                 )
  907.                 );
  908.  
  909.   /*Each of my output ports to the corresponding real output: */
  910.     CheckConnect(MIDIConnectData(SIGNATURE, ANODYNE_OUT1,
  911.                                  APPLE_MIDI_DRIVER, APPLE_DRIVER_OUT1
  912.                                 )
  913.                 );
  914.     CheckConnect(MIDIConnectData(SIGNATURE, ANODYNE_OUT2,
  915.                                  APPLE_MIDI_DRIVER, APPLE_DRIVER_OUT2
  916.                                 )
  917.                 );
  918. }
  919.